# gozerbot/botbase.py
#
#

""" bot base class. provides data/methods common to all bots. """

__status__ = "seen"

## gozerbot imports

from threads.thr import start_new_thread
from utils.log import rlog
from less import Less
from persist.pdol import Pdol
from utils.dol import Dol
from utils.lazydict import LazyDict
from persist.persiststate import PersistState
from channels import Channels
from datadir import datadir
from persist.pdod import Pdod
from config import config, Config, fleetbotconfigtxt
from runner import runners_start
from monitor import Monitor
from callbacks import callbacks, gn_callbacks
from cache import userhosts
from wait import Wait, Privwait
from eventhandler import mainhandler
from exit import globalshutdown
from utils.exception import handle_exception
from plugins import plugins

## throttle support

from gozerbot.plugs.throttle import state as throttlestate

## basic imports

import time
import threading
import os
import types
import sys
import copy

## defines

cpy = copy.deepcopy

## BotBase class

class BotBase(object):

    """ Base class for all bots. Inherit from this. """

    def __init__(self, name, cfg={}):
        self.name = name
        self.encoding = sys.getdefaultencoding()
        if not cfg: cfg = Config(inittxt=fleetbotconfigtxt)
        if not cfg.has_key('dir'): cfg['dir'] = os.getcwd()
        if not cfg.has_key('user'): cfg['user'] = 'gozerbot'
        if not cfg.has_key('type'): cfg['type'] = 'gozernet'
        self.__dict__.update(cfg)
        if not cfg.has_key('name'):
            if 'fleet' in cfg['dir']: self.name = cfg.dir.split(os.sep)[-1]
            else: self.name = name
        if not cfg.has_key('nick'): self.nick = 'gozerbot'
        if not cfg.has_key('server'): self.server = 'server not set'
        try:
            self.host = cfg['host']
            if not self.host: self.host = self.user.split('@')[1]
        except (KeyError, IndexError):
            try: self.host = self.user.split('@')[1]
            except (ValueError, IndexError): self.host = 'host not set'
        if not cfg.has_key('port'): self.port  = 0
        if not cfg.has_key('ipv6'): self.ipv6  = 0
        else: self.ipv6 = cfg['ipv6']
        if '..' in self.name or '/' in self.name: raise Exception('wrong bot name %s' % self.name)
        self.datadir = datadir + os.sep + 'fleet' + os.sep + self.name
        if not os.path.exists(self.datadir): os.mkdir(self.datadir)
        self.state = Pdod(self.datadir + os.sep + 'state') # bot state
        if not self.state.has_key('joinedchannels'): self.state['joinedchannels'] = []
        if not self.state.has_key('allowed'): self.state['allowed'] = []
        if not self.state.has_key('no-op'): self.state['no-op'] = []
        if not self.state.has_key('opchan'): self.state['opchan'] = []
        self.timejoined = {}
        self.type = self.type or 'gozernet'
        if self.type == 'jabber': self.type = 'xmpp'
        self.networkname = self.server
        self.jid = "%s@%s" % (self.nick, self.server)
        self.jids = {}
        self.shutloop = False
        self.cfg = cfg # the bots config
        self.orignick = "" # original nick
        self.blocking = 1 # use blocking sockets
        self.lastoutput = 0 # time of last output
        self.stopped = False # flag to set when bot is to be stopped
        self.connected = False # conencted flag
        self.connecting = False # connecting flag
        self.connectok = threading.Event() # event set when bot has connected
        self.waitingforconnect = False # flag to indicate we are waiting for connect
        self.starttime = time.time() # start time of the bot
        self.nrevents = 0 # number of events processed
        self.gcevents = 0 # number of garbage collected events
        self.less = Less(5) # output buffering
        self.userchannels = Dol() # list of channels a user is in
        self.channels = Channels(self.datadir + os.sep + 'channels') # channels
        self.userhosts = PersistState(self.datadir + os.sep + 'userhosts') # userhosts cache
        self.splitted = [] # list of splitted nicks
        self.throttle = [] # list of nicks that need to be throttled
        self.jabber = False # flag is set on jabber bots
        self.google = False
        self.callbacks = callbacks
        self.monitor = Monitor()
        self.wait = Wait()
        self.privwait = Privwait()
        self.error = None
        runners_start()
        
    def ownercheck(self, ievent, txt=None):
        """ check whether an event originated from the bot owner. """
        owner = self.cfg['owner'] or config['owner']
        if type(owner) == types.ListType:           
            if ievent.userhost in owner: return 1
        elif owner == ievent.userhost: return 1    
        else:
            rlog(100, self.name, 'failed owner check %s should be in %s' % (ievent.userhost, owner))
            if not txt: ievent.reply("only owner (see config file) is allowed to perform this command")
            else: ievent.reply("only owner (see config file) %s" % txt)
            return 0

    def save(self):
        """ save bot state. """
        self.channels.save()
        self.userhosts.save()
        self.state.save()

    def stop(self):
        """ stop the bot. """
        self.stopped = True
        rlog(10, self.name, 'stopped')

    def exit(self):
        """ shutdown the bot. overload this. """
        pass

    def connect(self, reconnect=True):
        """ connect the bot to the server. reconnects in the default case. """
        pass

    def say(self, printto, what, event=None, who=None, how='msg', fromm=None, speed=0, groupchat=False):
        """ say txt onto the console. overload this for your bot. """
        print what

    def whois(self, nick):
        """ do a whois query on a nick. overload this. """
        pass

    def sendraw(self, txt):
        """ send raw txt to the server. overload this. """
        print txt

    def voice(self, channel, txt):
        """ send voice txt to the channel. BHJTW ?? nick option ??. overload this. """ 
        pass

    def action(self, channel, txt):
        """ do an action in a channel. .. overload this. """
        pass

    def _raw(self, txt):
        """ core method for sending txt. .. overload this. """
        print txt

    def settopic(self, channel, txt):
        """ set topic of a channel. .. overload this. """
        pass

    def names(self, channel):
        """ query names of a channel. .. overload this. """
        pass

    def gettopic(self, channel):
        """ get topic of a channel. .. overload this. """
        pass

    def _dcclisten(self, *args):
        """  listen for incoming DCC CHAT requests. .. overload this. """
        pass

    def donick(self, nick, save=False, setorig=True):
        """ change nick of the bot. .. overload this. """
        pass

    def fakein(self, txt):
        """ pass fake txt input as if it was received by the bot. .. overload this. """
        pass

    def part(self, channel):
        """ leave a channel. .. overload this. """
        pass

    def serveforever(self):
        """ start the bots loops. will block until ctrl-c is received. """
        self.stopped = False
        self.shutloop = False
        while not self.stopped and not self.shutloop:
            try: import asyncore ; asyncore.poll(timeout=0.01)
            except ImportError: pass
            except Exception, ex:
                handle_exception()
                globalshutdown()
                os._exit(1)
            time.sleep(0.01)
            mainhandler.handle_one()

    def join(self, channel, password=""):
        """ join a channel using optional password. .. overload this. """
        pass

    def joinchannels(self):
        """ join all registered channels. .. overload this. """
        pass

    def connectwithjoin(self, reconnect=True):
        """ connect to the server and join channels. """
        self.connect(reconnect)
        self.connectok.wait()
        start_new_thread(self.joinchannels, ())

    def broadcast(self):
        """ announce a message to all channels. .. overload this"""
        pass

    def send(self, txt):
        """ send txt to the server. .. overload this"""
        pass

    def shutdown(self):
        """ close sockets of the bot. .. overload this"""
        pass

    def domsg(self, msg, response=False, wait=False):
        """ execute a message on the bot. """
        if response: msg.reply('executing %s (%s) on %s bot' % (msg.txt, msg.userhost, self.name))
        from gozerbot.plugins import plugins
        if wait: plugins.waitdispatch(self, msg)
        else: plugins.trydispatch(self, msg)

    def ratelimit(self, userhost, cpm=20):
        """ set ratelimit on a user. """
        try:
            throttlestate['level'][userhost] = cpm 
            throttlestate.save()
            rlog(10, self.name, '%s throttled to %s cpm' % (userhost, cpm))
        except Exception, ex:
            rlog(100, self.name, "can't set throttle of %s" % userhost)
            handle_exception()

    def remoteout(self, request, event):
        """ write the event in json to the webservers, use the request that is passed in. """
        request.wfile.write(event.tojson())

    def doevent(self, event):
        """  dispatch an event. """
        if not event: return
        e = cpy(event)
        if event.isremote: gn_callbacks.check(self, e)
        else: callbacks.check(self, e)
        if event.remotecmnd: plugins.trydispatch(self, event)
